package com.xyz.aop;

import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xyz.aop.Param.ParamType;
import com.xyz.util.ResponseUtil;
import com.xyz.util.dto.DataResult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Component // 声明这是一个组件
@Aspect // 声明这是一个切面Bean
@Order(2)
public class ParamAspect {
    private static final Logger logger = LoggerFactory.getLogger(ParamAspect.class);

    // 配置环绕通知
    @Around("execution(* com.xyz.controller..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = null;
        try {
            // 获取request response对象
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            // 排除文件相关接口
            List<String> urlList = Arrays.asList("/system/uploadFile", "/system/readFile");
            for (String url : urlList) {
                if (request.getRequestURI().contains(url)) {
                    return joinPoint.proceed();
                }
            }
            // 获取代理切入点和入参对象
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            String[] parameterNames = methodSignature.getParameterNames();
            Parameter[] parameters = method.getParameters();
            if (parameters.length == 0) {
                return joinPoint.proceed();
            }
            // 获取传入参数
            args = joinPoint.getArgs();

            // 打印params参数
            for (Map.Entry<String, String[]> maps : request.getParameterMap().entrySet()) {
                logger.info("Params参数：" + maps.getKey() + "=" + Arrays.toString(maps.getValue()));
            }

            // 获取body中传入参数
            StringBuilder sb = new StringBuilder();
            BufferedReader br = request.getReader();
            String temp;
            while ((temp = br.readLine()) != null) {
                sb.append(temp);
            }
            br.close();
            Map<String, String> bodyMap = new HashMap<>();
            if (StrUtil.isNotBlank(sb.toString())) {
                JSONObject jsonObject = JSONUtil.parseObj(sb.toString());
                for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                    bodyMap.put(entry.getKey(), entry.getValue().toString());
                }
            }
            // 打印body参数
            for (String key : bodyMap.keySet()) {
                logger.info("Body参数：" + key + "=" + bodyMap.get(key));
            }
            // 根据@Param和@ObjectParam注解解析参数
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                Param param = parameter.getAnnotation(Param.class);
                if (param != null) {
                    // 注入body参数
                    if (bodyMap.size() > 0) {
                        String bodyParameter = bodyMap.get(parameterNames[i]);
                        if (StrUtil.isNotBlank(bodyParameter)) {
                            args[i] = bodyParameter;
                        }
                    }
                    // 校验参数
                    String argStr = args[i] == null ? null : args[i].toString();
                    String paramValidation = paramValidation(param, parameterNames[i], argStr);
                    if (!"success".equals(paramValidation)) {
                        logger.warn(paramValidation);
                        if (response != null) {
                            ResponseUtil.outWithJson(response, DataResult.build9200(paramValidation));
                        }
                        return null;
                    }
                    continue; // 如果有了@Param注解，就不检验@ObjectParam了
                }
                ObjectParam objectParam = parameter.getAnnotation(ObjectParam.class);
                if (objectParam != null) {
                    // 注入body参数
                    Object arg = args[i];
                    if (bodyMap.size() > 0) {
                        for (String paramName : bodyMap.keySet()) {
                            try {
                                Method DTOMethod = arg.getClass().getMethod("set" + StrUtil.upperFirst(paramName), String.class);
                                DTOMethod.invoke(arg, bodyMap.get(paramName));
                            } catch (Exception e) {
                                logger.warn("无用的参数：" + paramName);
                            }
                        }
                    }
                    // 校验参数
                    Field[] field = parameter.getType().getDeclaredFields();
                    for (Field value : field) {
                        Param oParam = value.getAnnotation(Param.class);
                        if (oParam != null) {
                            String argStr = request.getParameter(value.getName());
                            Object invoke;
                            try {
                                Method DTOMethod = arg.getClass().getMethod("get" + StrUtil.upperFirst(value.getName()));
                                invoke = DTOMethod.invoke(arg);
                            } catch (Exception e) {
                                continue;
                            }
                            String invokeArg = invoke == null ? null : invoke.toString();
                            if (StrUtil.isNotBlank(invokeArg)) {
                                argStr = invokeArg;
                            }
                            String paramValidation = paramValidation(oParam, value.getName(), argStr);
                            if (!"success".equals(paramValidation)) {
                                logger.warn(paramValidation);
                                if (response != null) {
                                    ResponseUtil.outWithJson(response, DataResult.build9200(paramValidation));
                                }
                                return null;
                            }
                        }
                    }
                }
            }
        } catch (Throwable e) {
            return joinPoint.proceed(args);
        }
        return joinPoint.proceed(args);
    }

    private String paramValidation(Param param, String parameterName, String argStr) {
        if (param.isRequired() || StrUtil.isNotBlank(argStr)) { // 非必传且前端没传，就跳过
            if (param.isRequired() && StrUtil.isBlank(argStr)) { // 必传但没传
                return parameterName + "数据缺失";
            }
            if (dataValidation(param, argStr)) {
                return parameterName + "数据有误";
            }
        }
        return "success";
    }

    private boolean dataValidation(Param param, String arg) {
        if (param.type() == ParamType.POSITIVE_INTEGER) {
            return !isPositiveInteger(arg);
        } else if (param.type() == ParamType.DATE) {
            String regulation = ParamType.DATE.value();
            if (!param.rule().isEmpty()) {
                regulation = param.rule();
            }
            return !isFormatDate(arg, regulation);
        } else if (param.type() == ParamType.QUALIFIER) {
            return !isInclude(arg, param.rule());
        } else if (param.type() == ParamType.PLAIN) {
            if (!param.rule().isEmpty()) {
                return !ReUtil.isMatch(param.rule(), arg);
            }
        }
        return false;
    }


    private boolean isPositiveInteger(String arg) {
        String regex = "\\d+";
        return ReUtil.isMatch(regex, arg);
    }

    private boolean isFormatDate(String dateStr, String regulation) {
        if (StrUtil.isBlank(dateStr)) {
            return false;
        }
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(regulation);
            sdf.parse(dateStr);
            return true;
        } catch (ParseException e) {
            return false;
        }
    }

    private boolean isInclude(String param, String regulation) {
        try {
            String[] include = regulation.split(",");
            for (String string : include) {
                if (param.equals(string)) {
                    return true;
                }
            }
            return false;
        } catch (RuntimeException e) {
            return false;
        }
    }

}
